package client;

import controller.ClientController;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import security.AsymmetricKeyEncryptor;
import security.Generator;
import security.SymmetricKeyEncryptor;

public class Client {

	public static final int PUBLIC_PRIVATE = 1 << 1;
	public static final int PASSWORD = 1 << 2;

	public static final int USER = 1 << 3;
	public static final int DATA = 1 << 4;

	private String username;
	private String password;
	private Key privateKey;
	private Key symmetricKey;
	private int mode;
	private ClientSocket clientSocket;
	private ServerData serverData;
	private int nonceSize = 64;

	public Client(String username, String password) {
		this.username = username;
		this.password = password;
	}

	public void initializeClient(String address, int port) {
		clientSocket = new ClientSocket(address, port, this);
		serverData = new ServerData(address, port);
		// Read data of server public key and/or password
		clientSocket.connect();
		if (authenticate()) {
			new Thread(clientSocket).start();
		}
	}

	private boolean authenticate() {
		mode = Integer.parseInt(clientSocket.receiveData());
		clientSocket.sendMessage(username);
		byte nonce[] = new byte[nonceSize];
		clientSocket.receiveBytes(nonce);
		System.out.println("Received Nonce with \n" + Arrays.toString(nonce));
		byte encryptedNonce[] = {};
		switch (mode) {
		case PASSWORD:
			// encrypt nonce with symmetric key of client
			MessageDigest algorithm;
			try {
				algorithm = MessageDigest.getInstance("MD5");
				algorithm.reset();
				algorithm.update(password.getBytes());
				// byte messageDigest[] = algorithm.digest();
				// try {
				// DataOutputStream out = new DataOutputStream
				// (new FileOutputStream(new File("serverData/" + username +
				// "/password.prop")));
				// out.writeInt(messageDigest.length);
				// out.write(messageDigest);
				// out.flush();
				// System.out.println(Arrays.toString(messageDigest));
				// } catch (IOException ex) {
				// Logger.getLogger(Client.class.getName()).log(Level.SEVERE,
				// null, ex);
				// }
				//
				// symmetricKey = Generator.getInstance().generateSymmetricKey(
				// messageDigest);
				// System.out.println("Symmetric key = " +
				// Arrays.toString(symmetricKey.getEncoded()));
				symmetricKey = SymmetricKeyEncryptor.getInstance()
						.loadSymmetricKey(username, username + "key");
				encryptedNonce = SymmetricKeyEncryptor.getInstance().encrypt(
						nonce, symmetricKey);
			} catch (NoSuchAlgorithmException e) {
				e.printStackTrace();
			}
			break;
		case PUBLIC_PRIVATE:
			// encrypt nonce with private key of client
			try {
				privateKey = AsymmetricKeyEncryptor.getInstance()
						.loadPrivateKey("clientdata/" + username + "/");
				encryptedNonce = AsymmetricKeyEncryptor.getInstance().encrypt(
						nonce, privateKey);
			} catch (NoSuchAlgorithmException e) {
				e.printStackTrace();
			} catch (InvalidKeySpecException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			break;
		}
		// send encrypted nonce
		System.out.println(Arrays.toString(encryptedNonce));
		clientSocket.sendMessage("" + encryptedNonce.length);
		clientSocket.sendMessage(encryptedNonce);

		String result = clientSocket.receiveData();
		if ("OK".equals(result))
			return true;
		return false;
	}

	public void handleNewClient() {

	}

	public void handleMessage(byte[] message) {
		// decrypt message
		String decryptedMsg = "";
		switch (mode) {
		case PUBLIC_PRIVATE:
			decryptedMsg = new String(AsymmetricKeyEncryptor.getInstance()
					.decrypt(message, privateKey));
			break;
		case PASSWORD:
			decryptedMsg = new String(SymmetricKeyEncryptor.getInstance()
					.decrypt(message, symmetricKey));
			break;
		}
		System.out.println("Received Message " + decryptedMsg);
		int type = Integer.parseInt(decryptedMsg.substring(0, decryptedMsg
				.indexOf(' ')));
		System.out.println("Type " + type);
		String body = decryptedMsg.substring(decryptedMsg.indexOf(' ') + 1);
		System.out.println("body " + body);
		switch (type) {
		case USER:
			System.out.println("New user added with " + body);
			// TODO Add new user to group (body is new user)
			ClientController.getInstance(null).addUser(body);
			// call ui and username = body
			break;
		case DATA:
			System.out.println("New message received with " + body);
			String receiverUsername = body.substring(0, body.indexOf(' '));
			String data = body.substring(body.indexOf(' ') + 1);
			// TODO Add data to chat
			ClientController.getInstance(null).addMessage(receiverUsername,
					data);
			break;
		}
	}

	public void sendNewMessage(String message) {
		// encrypt message
		String newMessage = username + " " + message;
		byte[] encryptedMsg = null;
		switch (mode) {
		case PUBLIC_PRIVATE:
			PublicKey serverPublicKey = serverData.getPublicKey();
			encryptedMsg = AsymmetricKeyEncryptor.getInstance().encrypt(
					newMessage.getBytes(), serverPublicKey);
			break;
		case PASSWORD:
			encryptedMsg = SymmetricKeyEncryptor.getInstance().encrypt(
					newMessage.getBytes(), symmetricKey);
			break;
		}
		System.out.println("Before sending message with " + encryptedMsg.length
				+ " and \n" + Arrays.toString(encryptedMsg));
		clientSocket.sendMessage(encryptedMsg.length + "");
		clientSocket.sendMessage(encryptedMsg);
		ClientController.getInstance(null).addMessage(username, message);
	}

}
